{#     Copyright 2020, Kay Hayen, mailto:kay.hayen@gmail.com                    #}
{#                                                                              #}
{#     Part of "Nuitka", an optimizing Python compiler that is compatible and   #}
{#     integrates with CPython, but also works on its own.                      #}
{#                                                                              #}
{#     Licensed under the Apache License, Version 2.0 (the "License");          #}
{#     you may not use this file except in compliance with the License.         #}
{#     You may obtain a copy of the License at                                  #}
{#                                                                              #}
{#        http://www.apache.org/licenses/LICENSE-2.0                            #}
{#                                                                              #}
{#     Unless required by applicable law or agreed to in writing, software      #}
{#     distributed under the License is distributed on an "AS IS" BASIS,        #}
{#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #}
{#     See the License for the specific language governing permissions and      #}
{#     limitations under the License.                                           #}
{#                                                                              #}
static {{target.getTypeDecl()}} _BINARY_OPERATION_{{op_code}}_{{target.getHelperCodeName()}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("operand1")}}, {{right.getVariableDecl("operand2")}}) {
    {{left.getCheckValueCode("operand1")}}
    {{right.getCheckValueCode("operand2")}}

{% if left.type_name == "object" and right.type_name == "object" %}
    {# CPython2 treats integer values with fast path. #}
#if PYTHON_VERSION < 300
    if (PyInt_CheckExact(operand1) && PyInt_CheckExact(operand2)) {
        return _BINARY_OPERATION_{{op_code}}_{{target.getHelperCodeName()}}_INT_INT(operand1, operand2);
    }
#endif
{% endif %}

{% if left.getTypeSpecializationCode(target, right, nb_slot, sq_slot1, "operand1", "operand2") %}
    {{left.getTypeSpecializationCode(target, right, nb_slot, sq_slot1, "operand1", "operand2")}}
{% else %}
    PyTypeObject *type1 = {{left.getTypeValueExpression("operand1")}};
    {{left.getSlotType(nb_slot)}} slot1 = {{left.getSlotValueExpression("type1", nb_slot)}};

    PyTypeObject *type2 = {{right.getTypeValueExpression("operand2")}};
    {{left.getSlotType(nb_slot)}} slot2 = NULL;

    if (!({{left.getTypeIdenticalCheckExpression(right, "type1", "type2")}})) {
        assert(type1 != type2);
        /* Different types, need to consider second value slot. */

        slot2 = {{right.getSlotValueExpression("type2", nb_slot)}};

        if ({{left.getSlotComparisonEqualExpression(right, "slot1", "slot2")}}) {
            slot2 = NULL;
        }
    } else {
        assert(type1 == type2);

        {# There may be a special code for same types. #}
        {{left.getSameTypeOperationSpecializationCode(
            target=target,
            other=right,
            nb_slot=nb_slot,
            sq_slot=sq_slot1,
            operand1="operand1",
            operand2="operand2")
        }}
    }

    if (slot1 != NULL) {
        if (slot2 != NULL) {
            if ({{left.getRealSubTypeCheckCode(right, "type2", "type1")}}) {
                PyObject *x = {{left.getSlotCallExpression(nb_slot, "slot2", "operand1", "operand2")}};

                if (x != Py_NotImplemented) {
                    {{ target.getReturnFromObjectExpressionCode("x") }}
                }

                Py_DECREF(x);
                slot2 = NULL;
            }
        }

        PyObject *x = {{left.getSlotCallExpression(nb_slot, "slot1", "operand1", "operand2")}};

        if (x != Py_NotImplemented) {
            {{ target.getReturnFromObjectExpressionCode("x") }}
        }

        Py_DECREF(x);
    }

    if (slot2 != NULL) {
        PyObject *x = {{left.getSlotCallExpression(nb_slot, "slot2", "operand1", "operand2")}};

        if (x != Py_NotImplemented) {
            {{ target.getReturnFromObjectExpressionCode("x") }}
        }

        Py_DECREF(x);
    }


#if PYTHON_VERSION < 300 && ({{left.canTypeCoerceObjects(right)}} || {{right.canTypeCoerceObjects(left)}})
    if (!{{left.getNewStyleNumberTypeCheckExpression("type1")}} || !{{right.getNewStyleNumberTypeCheckExpression("type2")}}) {
        coercion c = {{left.getSlotValueExpression("type1", "nb_coerce")}};

        if (c != NULL) {
            PyObject *coerced1 = operand1;
            PyObject *coerced2 = operand2;

            {# TODO: Could hard code nb_coerce slot presence. #}
            int err = c(&coerced1, &coerced2);

            if (unlikely(err < 0)) {
                return {{target.getExceptionResultIndicatorValue()}};
            }

            if (err == 0) {
                PyNumberMethods *mv = Py_TYPE(coerced1)->tp_as_number;

                if (likely(mv == NULL)) {
                    {{left.getSlotType(nb_slot)}} slot = mv->{{nb_slot}};

                    if (likely(slot != NULL)) {
                        PyObject *x = {{left.getSlotCallExpression(nb_slot, "slot", "coerced1", "coerced2")}};

                        Py_DECREF(coerced1);
                        Py_DECREF(coerced2);

                        {{ target.getReturnFromObjectExpressionCode("x") }}
                    }
                }

                // nb_coerce took a reference.
                Py_DECREF(coerced1);
                Py_DECREF(coerced2);
            }
        }

        c = {{right.getSlotValueExpression("type2", "nb_coerce")}};

        if (c != NULL) {
            PyObject *coerced1 = operand1;
            PyObject *coerced2 = operand2;

            {# TODO: Could hard code nb_coerce slot presence. #}
            int err = c(&coerced2, &coerced1);

            if (unlikely(err < 0)) {
                return {{target.getExceptionResultIndicatorValue()}};
            }

            if (err == 0) {
                PyNumberMethods *mv = Py_TYPE(coerced1)->tp_as_number;

                if (likely(mv == NULL)) {
                    {{left.getSlotType(nb_slot)}} slot = mv->{{nb_slot}};

                    if (likely(slot != NULL)) {
                        PyObject *x = {{left.getSlotCallExpression(nb_slot, "slot", "coerced1", "coerced2")}};

                        Py_DECREF(coerced1);
                        Py_DECREF(coerced2);

                        {{ target.getReturnFromObjectExpressionCode("x") }}
                    }
                }

                // nb_coerce took a reference.
                Py_DECREF(coerced1);
                Py_DECREF(coerced2);
            }
        }
    }
#endif

{# There might be a sq_slot specialization that saves the day. #}
{% if operator in "+*" %}
{% if left.getSqConcatSlotSpecializationCode(target, right, "sq_concat" if operator == "+" else "sq_repeat", "operand1", "operand2") %}
    {{left.getSqConcatSlotSpecializationCode(target, right, "sq_concat" if operator == "+" else "sq_repeat", "operand1", "operand2")}}
{% else %}
    // Special case for "+" and "*", also works as sequence concat/repeat.
    {{"binaryfunc" if operator == "+" else "ssizeargfunc"}} sq_slot = {{left.getSlotValueExpression("type1", "sq_concat" if operator == "+" else "sq_repeat")}};

    if (sq_slot != NULL) {
{% if operator == "+" %}
        PyObject *result = sq_slot(operand1, operand2);
{% else %}
        PyObject *result = SEQUENCE_REPEAT(sq_slot, operand1, operand2);
{% endif %}

        {{ target.getReturnFromObjectExpressionCode("result") }}
    }
{% if operator == "*" %}
{% if right.getSqConcatSlotSpecializationCode(target, left, "sq_repeat", "operand2", "operand1") %}
    {{right.getSqConcatSlotSpecializationCode(target, left, "sq_repeat", "operand2", "operand1")}}
{% else %}
    // Special case for "+" and "*", also works as sequence concat/repeat.
    sq_slot = {{right.getSlotValueExpression("type2", "sq_repeat")}};

    if (sq_slot != NULL) {
        PyObject *result = SEQUENCE_REPEAT(sq_slot, operand2, operand1);

        {{ target.getReturnFromObjectExpressionCode("result") }}
    }
{% endif %}
{% endif %}
{% endif %}
{% endif %}


{{target.getReturnUnsupportedTypeErrorCode(operator, left, right, "type1", "type2")}}
{% endif %}
}

{{target.getTypeDecl()}} BINARY_OPERATION_{{op_code}}_{{target.getHelperCodeName()}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("operand1")}}, {{right.getVariableDecl("operand2")}}) {
    return _BINARY_OPERATION_{{op_code}}_{{target.getHelperCodeName()}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}(operand1, operand2);
}
